# frozen_string_literal: true

module EE
  module Gitlab
    module BackgroundMigration
      # Corrects vulnerability findings which are erroneously associated with
      # vulnerability scanners across across different projects.
      module MigrateSharedVulnerabilityScanners
        extend ActiveSupport::Concern
        extend ::Gitlab::Utils::Override

        class Finding < ::ApplicationRecord # rubocop:disable Style/Documentation
          include ::EachBatch

          REPORT_TYPES = { cluster_image_scanning: 7, generic: 99 }.freeze

          self.table_name = "vulnerability_occurrences"

          belongs_to :scanner, inverse_of: :findings

          # pipeline fails otherwise
          validates :details, json_schema: { filename: "vulnerability_finding_details" }, if: false

          def self.to_process
            where(report_type: REPORT_TYPES.values)
            .joins(:scanner)
            .where("vulnerability_occurrences.project_id != vulnerability_scanners.project_id")
          end
        end

        class Scanner < ::ApplicationRecord # rubocop:disable Style/Documentation
          self.table_name = "vulnerability_scanners"

          has_many :findings, inverse_of: :scanner

          def self.find_or_create_id_for(finding)
            current_time = Time.zone.now
            attrs = finding.scanner.attributes.except("id").merge(project_id: finding.project_id,
                                                                  created_at: current_time,
                                                                  updated_at: current_time)

            result = upsert(attrs, unique_by: :index_vulnerability_scanners_on_project_id_and_external_id)
            result.rows.first.first
          end
        end

        class VulnerabilityRead < ::ApplicationRecord # rubocop:disable Style/Documentation
          self.table_name = "vulnerability_reads"
        end

        prepended do
          operation_name :migrate_shared_vulnerability_scanners
          scope_to -> (relation) { Finding.to_process.merge(relation) }
        end

        override :perform
        def perform
          each_sub_batch do |batch|
            batch
              .group_by { |finding| [finding.project_id, finding.scanner.external_id] }.map(&:second)
              .each do |findings|
                # all findings within this group should have same `scanner`, so we are taking the first one
                scanner_id = Scanner.find_or_create_id_for(findings.first)

                Finding.where(id: findings.map(&:id)).update_all(scanner_id: scanner_id)

                VulnerabilityRead.where(uuid: findings.map(&:uuid)).update_all(scanner_id: scanner_id)
              end
          end
        end
      end
    end
  end
end
